﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Content.Web;
using System.Collections.Generic;
using System.Linq;
using Content.Converters;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Text.RegularExpressions;
using System.Text;
using System.Diagnostics;
using System.Windows.Markup;
using System.Windows.Media.Imaging;

namespace Content
{
    public abstract class DbUnit : UserControl, IXmlNamespaceResolver
    {
        protected Unit unit;
        protected string error;
        protected string errorDescription;
        protected abstract Storyboard Storyboard { get; }
        protected abstract ObjectAnimationUsingKeyFrames DetailsTextAnimation { get; }
        protected abstract DoubleAnimationUsingKeyFrames DetailsOpacityAnimation { get; }
        protected abstract Grid GridLayout { get; }
        protected abstract int BulletsColumn { get; }
        private XNamespace defaultNamespace;
        private Dictionary<string, XElement> inlineReplacements;

        public DbUnit()
        {
        }

		public DbUnit(string error, string description)
		{
            this.error = error;
            this.errorDescription = description;
		}

        public DbUnit(Unit unit)
        {
            this.unit = unit;
            this.Loaded += new RoutedEventHandler(UnitLoaded);
        }

        private void UnitLoaded(object sender, RoutedEventArgs e)
        {
            var lightColors = this.GetLightColors().AsQueryable();
            var background = lightColors.GetRandom();
            var brush = new SolidColorBrush(background);

            brush.Opacity = .15;

            this.GridLayout.Background = brush;
        }

        protected IEnumerable<Color> GetLightColors()
        {
            var type = typeof(WebColors);

            return type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).Where(f =>
            {
                return f.Name.StartsWith("Light") && !new List<string> 
                    {
                        "LightGray",
                        "LightSlateGray"
                    }.Contains(f.Name);

            }).Select(f =>
            {
                return f.GetValue(0);

            }).Cast<Color>();
        }

        protected IEnumerable<Color> GetDarkColors()
        {
            var type = typeof(WebColors);

            return type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).Where(f => 
                {
                    return f.Name.StartsWith("Dark") && !new List<string> 
                    {
                        "DarkOrange",
                        "DarkTurquoise",
                        "DarkKhaki",
                        "DarkSalmon",
                        "DarkGray",
                        "DarkSeaGreen",
                        "DarkGoldenrod"
                    }.Contains(f.Name);

                }).Select(f => 
                    {
                        return f.GetValue(0);

                    }).Cast<Color>();
        }

        protected string GetColorName(Color color)
        {
            var type = typeof(WebColors);

            return type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).Where(f =>
            {
                var color2 = (Color)f.GetValue(0);

                return color2.R == color.R && color2.G == color.G && color2.B == color.B;

            }).Select(f => f.Name).SingleOrDefault();
        }

        protected virtual void LoadContentRows(Color foreground)
        {
            var itemNumber = 1;
            var brush = new SolidColorBrush(foreground);
            var transitionSeconds = 8;
            var converter = new ByteArrayToBitMapConverter();
            var validator = new RichTextValidator();
            ContentControl contentControl;
            string name;
            DoubleAnimationUsingKeyFrames itemOpacityAnimation;
            int itemStart;
            BitmapImage bitmap;
            Image image;

            foreach (var contentRow in unit.ContentRows)
            {
                contentControl = new ContentControl();
                name = "Item" + itemNumber.ToString();

                inlineReplacements = new Dictionary<string, XElement>();

                // Item Opacity

                itemOpacityAnimation = new DoubleAnimationUsingKeyFrames();
                itemStart = itemNumber * transitionSeconds;

                contentControl.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
                contentControl.VerticalContentAlignment = System.Windows.VerticalAlignment.Stretch;
                contentControl.Name = name;
                contentControl.Margin = new Thickness(0);
                contentControl.Padding = new Thickness(0);
                contentControl.SetValue(Grid.RowProperty, itemNumber);
                contentControl.SetValue(ContentControl.HeightProperty, Double.NaN);

                if (contentRow.ContentRowRichText != null)
                {
                    var textBox = new RichTextBox();
                    var document = XDocument.Parse(contentRow.ContentRowRichText);
                    var section = document.Descendants().Last(d => d.Name.LocalName == "Section");
                    Action<XElement> recurseElements = null;
                    Action<XElement> recurseAttributes = (element) =>
                    {
                        var removeAttributes = new List<XAttribute>();
                        var skip = new List<string>() 
                        {
                            "xmlns",
                            "space"
                        };

                        foreach (var attribute in element.Attributes())
                        {
                            if (!skip.Contains(attribute.Name.LocalName))
                            {
                                if (!validator.IsAllowed(attribute))
                                {
                                    removeAttributes.Add(attribute);
                                }
                            }
                        }

                        foreach (var attribute in removeAttributes)
                        {
                            attribute.Remove();
                        }
                    };

                    recurseElements = (element) =>
                    {
                        var removeElements = new List<XElement>();

                        if (!validator.IsAllowed(element))
                        {
                            element = ReplaceElement(element);
                        }

                        recurseAttributes(element);

                        foreach (var childElement in element.Elements())
                        {
                            recurseElements(childElement);
                        }
                    };

                    defaultNamespace = section.GetDefaultNamespace();

                    recurseAttributes(section);
                    section.Elements().ToList().ForEach(e2 => recurseElements(e2));

                    var xaml = section.ToString();

                    validator.ValidateXAML(xaml);

                    textBox.Xaml = xaml;
                    textBox.SetValue(RichTextBlock.HeightProperty, Double.NaN);
                    textBox.SetValue(RichTextBlock.WidthProperty, Double.NaN);

                    textBox.LineStackingStrategy = LineStackingStrategy.MaxHeight;
                    textBox.Margin = new Thickness(0);
                    textBox.Padding = new Thickness(0);
                    textBox.BorderThickness = new Thickness(0);
                    textBox.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
                    textBox.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
                    textBox.Background = new SolidColorBrush(Colors.Transparent);
                    textBox.IsReadOnly = true;

                    if (inlineReplacements.Count > 0)
                    {
                        Action<Block> recurseBlocks = null;

                        recurseBlocks = (block) =>
                        {
                            if (block is Paragraph)
                            {
                                var paragraph = (Paragraph)block;
                                var runReplaces = new Dictionary<Run, InlineUIContainer>();

                                paragraph.Inlines.OfType<Run>().ToList().ForEach(r =>
                                {
                                    if (r.Text.StartsWith("INLINEREPLACE("))
                                    {
                                        var regex = new Regex(@"INLINEREPLACE\((?<index>\d+?)\)");
                                        var index = regex.Match(r.Text).Groups["index"].Value;
                                        var replace = (XElement)inlineReplacements[index];
                                        var xamlReplace = replace.ToString();
                                        var container = (InlineUIContainer)XamlReader.Load(xamlReplace);

                                        textBox.LayoutUpdated += (sender2, e2) =>
                                        {
                                            //var text = new StringBuilder();
                                            //var settings = new XamlWriterSettings();

                                            //settings.WriteDefaultValues = true;

                                            //using (XamlWriter writer = XamlWriter.CreateWriter(text, false, settings))
                                            //{
                                            //    writer.WriteElement(textBox);
                                            //}

                                            //var xamlFull = text.ToString();
                                            var xamlFull = textBox.Xaml;

                                            xamlFull = null;
                                        };

                                        runReplaces.Add(r, container);
                                    }
                                });

                                runReplaces.ToList().ForEach(p =>
                                {
                                    var run = p.Key;
                                    var container = p.Value;

                                    paragraph.Inlines.Remove(run);
                                    paragraph.Inlines.Add(container);
                                });
                            }
                            else
                            {
                                Debugger.Break();
                            }
                        };

                        textBox.Blocks.ToList().ForEach(b => recurseBlocks(b));
                    }

                    contentControl.Content = textBox;
                }
                else if (contentRow.ContentRowText != null)
                {
                    var textBox = new RichTextBox();

                    textBox.Xaml = contentRow.ContentRowText;
                    textBox.SetValue(RichTextBlock.HeightProperty, Double.NaN);
                    textBox.SetValue(RichTextBlock.WidthProperty, Double.NaN);

                    textBox.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
                    textBox.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;

                    contentControl.Content = textBox;
                }
                else if (contentRow.ContentRowImageData != null)
                {
                    bitmap = (BitmapImage)converter.Convert(contentRow.ContentRowImageData, typeof(BitmapImage), null, null);
                    image = new Image();

                    image.Height = bitmap.PixelHeight;
                    image.Width = bitmap.PixelWidth;

                    image.Source = bitmap;
                    image.Stretch = Stretch.None;
                    image.VerticalAlignment = System.Windows.VerticalAlignment.Center;
                    image.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
                    image.Margin = new Thickness(10);

                    contentControl.Content = image;
                }

                Storyboard.SetTarget(itemOpacityAnimation, contentControl);
                Storyboard.SetTargetProperty(itemOpacityAnimation, new PropertyPath(UIElement.OpacityProperty));

                itemOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
                {
                    KeyTime = TimeSpan.FromSeconds(0),
                    Value = 0
                });

                itemOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
                {
                    KeyTime = TimeSpan.FromSeconds(itemStart),
                    Value = 0
                });

                itemOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
                {
                    KeyTime = TimeSpan.FromSeconds(itemStart + 1),
                    Value = 1
                });

                this.Storyboard.Children.Add(itemOpacityAnimation);

                // add elements

                GridLayout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(0, GridUnitType.Auto), MinHeight = 30 });
                GridLayout.Children.Add(contentControl);

                itemNumber++;
            }
        
            // load next image

            contentControl = new ContentControl();
            name = "Item" + itemNumber.ToString();

            // Item Opacity

            itemOpacityAnimation = new DoubleAnimationUsingKeyFrames();
            itemStart = itemNumber * transitionSeconds;

            contentControl.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
            contentControl.VerticalContentAlignment = System.Windows.VerticalAlignment.Bottom;
            contentControl.Name = name;
            contentControl.Margin = new Thickness(0);
            contentControl.Padding = new Thickness(0);
            contentControl.SetValue(Grid.RowProperty, itemNumber);
            contentControl.SetValue(ContentControl.HeightProperty, Double.NaN);

            var uri = new Uri("Images/Next.png", UriKind.Relative);

            bitmap = new BitmapImage(uri);

            image = new Image();

            image.Height = 50;
            image.Width = 50;

            image.Source = bitmap;
            image.Stretch = Stretch.Uniform;
            image.VerticalAlignment = System.Windows.VerticalAlignment.Center;
            image.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
            image.Margin = new Thickness(10);

            contentControl.Content = image;

            Storyboard.SetTarget(itemOpacityAnimation, contentControl);
            Storyboard.SetTargetProperty(itemOpacityAnimation, new PropertyPath(UIElement.OpacityProperty));

            itemOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
            {
                KeyTime = TimeSpan.FromSeconds(0),
                Value = 0
            });

            itemOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
            {
                KeyTime = TimeSpan.FromSeconds(itemStart),
                Value = 0
            });

            itemOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
            {
                KeyTime = TimeSpan.FromSeconds(itemStart + 1),
                Value = 1
            });

            this.Storyboard.Children.Add(itemOpacityAnimation);

            // add elements

            GridLayout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(0, GridUnitType.Auto), MinHeight = 30 });
            GridLayout.Children.Add(contentControl);
        }

        protected virtual void LoadBullets(Color foreground)
        {
            var itemNumber = 1;
            var transitionSeconds = 10;

            foreach (var bulletPoint in unit.BulletPoints)
            {
                var textBlock = new TextBlock();
                var name = "Item" + itemNumber.ToString();

                // Item Opacity

                var itemOpacityAnimation = new DoubleAnimationUsingKeyFrames();
                var itemStart = itemNumber * transitionSeconds;

                textBlock.Text = "• " + bulletPoint.BulletItem;
                textBlock.TextWrapping = TextWrapping.Wrap;
                textBlock.FontSize = 16;
                textBlock.FontFamily = new FontFamily("Portable User Interface");
                textBlock.FontWeight = FontWeights.Bold;
                textBlock.Foreground = new SolidColorBrush(foreground);
                textBlock.Name = name;
                textBlock.SetValue(Grid.RowProperty, itemNumber + 1);
                textBlock.SetValue(Grid.ColumnProperty, this.BulletsColumn);

                Storyboard.SetTarget(itemOpacityAnimation, textBlock);
                Storyboard.SetTargetProperty(itemOpacityAnimation, new PropertyPath(UIElement.OpacityProperty));

                itemOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
                {
                    KeyTime = TimeSpan.FromSeconds(0),
                    Value = 0
                });

                itemOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
                {
                    KeyTime = TimeSpan.FromSeconds(itemStart),
                    Value = 0
                });

                itemOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
                {
                    KeyTime = TimeSpan.FromSeconds(itemStart + 1),
                    Value = 1
                });

                this.Storyboard.Children.Add(itemOpacityAnimation);

                // Details Opacity

                var detailsOpacityAnimation = this.DetailsOpacityAnimation;

                if (itemNumber != 1)
                {
                    detailsOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
                    {
                        KeyTime = TimeSpan.FromSeconds(itemStart - 1),
                        Value = 1
                    });
                }

                detailsOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
                {
                    KeyTime = TimeSpan.FromSeconds(itemStart),
                    Value = 0
                });

                detailsOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
                {
                    KeyTime = TimeSpan.FromSeconds(itemStart + 1),
                    Value = 0
                });

                detailsOpacityAnimation.KeyFrames.Add(new SplineDoubleKeyFrame
                {
                    KeyTime = TimeSpan.FromSeconds(itemStart + 2),
                    Value = 1
                });

                // Details Text

                var detailsTextAnimation = this.DetailsTextAnimation;

                detailsTextAnimation.KeyFrames.Add(new DiscreteObjectKeyFrame
                {
                    KeyTime = TimeSpan.FromSeconds(itemStart),
                    Value = bulletPoint.Details
                });

                // add elements

                this.GridLayout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(240 / unit.BulletPoints.Count + 1, GridUnitType.Pixel), MinHeight = 30 });
                this.GridLayout.Children.Add(textBlock);

                itemNumber++;
            }
        }

        private XElement ReplaceElement(XElement element)
        {
            switch (element.Name.LocalName)
            {
                case "List":
                    return ReplaceList(element);
                default:
                    return null;
            }
        }

        private XElement ReplaceList(XElement element)
        {
            var _namespace = element.GetDefaultNamespace();
            var paragraph = new XElement(_namespace + "Paragraph", string.Format("INLINEREPLACE({0})", inlineReplacements.Count));
            var uiContainer = new XElement(_namespace + "InlineUIContainer",
                    new XElement(_namespace + "Grid", new XAttribute(_namespace + "Height", "Auto"),
                        new XElement(_namespace + "Grid.ColumnDefinitions",
                            new XElement(_namespace + "ColumnDefinition", new XAttribute(_namespace + "Width", 50)),
                            new XElement(_namespace + "ColumnDefinition", new XAttribute(_namespace + "Width", "*"))),
                       new XElement(_namespace + "Grid.RowDefinitions")));

            inlineReplacements.Add(inlineReplacements.Count.ToString(), uiContainer);

            var grid = uiContainer.XPathSelectElement("p:Grid", this);
            var rowDefs = grid.XPathSelectElement("p:Grid.RowDefinitions", this);
            var rowIndex = 0;
            var fontFamily = new XAttribute(_namespace + "FontFamily", "Calibri");
            var fontSize = new XAttribute(_namespace + "FontSize", "16.666");
            var fontFamilyFound = false;
            var fontSizeFound = false;

            element.Elements().ToList().ForEach(e =>
            {
                var child = e;

                while (child != null && !(fontFamilyFound && fontSizeFound))
                {
                    if (!fontFamilyFound && child.Attributes().Any(a => a.Name.LocalName == "FontFamily"))
                    {
                        fontFamily = child.Attributes().Single(a => a.Name.LocalName == "FontFamily");
                        fontFamilyFound = true;
                    }

                    if (!fontSizeFound && child.Attributes().Any(a => a.Name.LocalName == "FontSize"))
                    {
                        fontSize = child.Attributes().Single(a => a.Name.LocalName == "FontSize");
                        fontSizeFound = true;
                    }

                    child = child.Elements().First();
                }

                rowDefs.Add(new XElement(_namespace + "RowDefinition", new XAttribute(_namespace + "Height", "Auto")));

                grid.Add(new XElement(_namespace + "RichTextBox",
                            new XAttribute(_namespace + "Tag", "Bullet" + rowIndex.ToString()),
                            new XAttribute(_namespace + "Grid.Row", rowIndex),
                            new XAttribute(_namespace + "Grid.Column", 0),
                            new XAttribute(_namespace + "Height", "Auto"),
                            new XAttribute(_namespace + "HorizontalAlignment", "Stretch"),
                            new XAttribute(_namespace + "VerticalAlignment", "Stretch"),
                            new XAttribute(_namespace + "LineStackingStrategy", "MaxHeight"),
                            new XAttribute(_namespace + "TextWrapping", "Wrap"),
                            new XAttribute(_namespace + "HorizontalScrollBarVisibility", "Disabled"),
                            new XAttribute(_namespace + "VerticalScrollBarVisibility", "Disabled"),
                            new XAttribute(_namespace + "BorderThickness", "0"),
                            new XAttribute(_namespace + "Background", "Transparent"),
                            new XAttribute(_namespace + "IsReadOnly", "True"),
                            new XElement(_namespace + "Paragraph",
                                new XAttribute(_namespace + "FontFamily", fontFamily.Value),
                                new XAttribute(_namespace + "FontSize", fontSize.Value),
                                new XAttribute(_namespace + "TextAlignment", "Right"),
                                "•")),
                        new XElement(_namespace + "RichTextBox",
                            new XAttribute(_namespace + "Tag", "BulletItem" + rowIndex.ToString()),
                            new XAttribute(_namespace + "Grid.Row", rowIndex),
                            new XAttribute(_namespace + "Grid.Column", 1),
                            new XAttribute(_namespace + "Height", "Auto"),
                            new XAttribute(_namespace + "HorizontalAlignment", "Stretch"),
                            new XAttribute(_namespace + "VerticalAlignment", "Stretch"),
                            new XAttribute(_namespace + "TextWrapping", "Wrap"),
                            new XAttribute(_namespace + "LineStackingStrategy", "MaxHeight"),
                            new XAttribute(_namespace + "HorizontalScrollBarVisibility", "Disabled"),
                            new XAttribute(_namespace + "VerticalScrollBarVisibility", "Disabled"),
                            new XAttribute(_namespace + "BorderThickness", "0"),
                            new XAttribute(_namespace + "Background", "Transparent"),
                            new XAttribute(_namespace + "IsReadOnly", "True"),
                            new XElement(_namespace + "Paragraph",
                                new XAttribute(_namespace + "FontFamily", fontFamily.Value),
                                new XAttribute(_namespace + "FontSize", fontSize.Value),
                                new XElement(_namespace + "Run", e.Value))));


                rowIndex++;
            });

            var parent = element.Parent;

            element.Remove();
            parent.Add(paragraph);

            return paragraph;
        }

        public IDictionary<string, string> GetNamespacesInScope(XmlNamespaceScope scope)
        {
            return new Dictionary<string, string>() { { "p", defaultNamespace.NamespaceName } };
        }

        public string LookupNamespace(string prefix)
        {
            return defaultNamespace.NamespaceName;
        }

        public string LookupPrefix(string namespaceName)
        {
            return "p";
        }
    }
}
